Proyecciones de población

El Instituto Nacional de Estadística y Censos (INDEC) es el encargado de elaborar la proyecciones de población en Argentina, en base a los censos de población, hogares y vivienda.

La serie de proyecciones más actualizada es la desarrollada en base al Censo Nacional de Población, Hogares y Viviendas 2010 y se encuentra disponible en https://www.indec.gob.ar/indec/web/Nivel4-Tema-2-24-85

El material publicado en el sitio web del Instituto aparece en dos formatos disponibles para descarga:

  • Documento .pdf
  • Planilla de cálculos .xls

Ambos formatos resultan habitualmente poco amigables para docentes, investigadores y público relacionado con las estadísticas y la ciencia de datos. En primer lugar, el formato .pdf resulta adecuado para la impresión del documento y su visualización, pero no para la utilización de los datos en softwares de procesamiento. Por otro lado, las planillas .xls disponibles presentan un diseño en tabulados contiguos, fragmentados en distintas hojas que representan a las jurisdicciones, lo que torna muy laborioso reconstruir series temporales sin un trabajo arduo de copiado y pegado que aumenta el riesgo de cometer errores.


Objetivo

El objetivo de este documento es presentar una forma programática de procesamiento de las proyecciones de poblacion 2010-2040 publicadas por el INDEC, usando el software estadístico R (R Core Team 2019) con sus librerías dplyr, shiny (Chang et al. 2022), openxlsx (Schauberger and Walker 2023) y readxl (Wickham and Bryan 2023).


Desarrollo

1- Instalar y cargar librerías

Como se mencionó más arriba, se utilizarán las librerías dplyr, openxl y readxl, que deben estar previamente instaladas.

library(dplyr)
library(readxl)
library(openxlsx)
library(highcharter)


2- Descargar proyecciones de población en formato .xls de la web del INDEC

url = "https://www.indec.gob.ar/ftp/cuadros/poblacion/c2_proyecciones_prov_2010_2040.xls"
download.file(url, destfile = "poblacion.xls", mode="wb")

3- Crear una variable con los nombres de las hojas de la planilla descargada.

Esto se puede hacer a través de la función excel_sheets de readxl

sheets = readxl::excel_sheets("poblacion.xls")
print(sheets)
##  [1] "GraphData"              "01-TOTAL DEL PAÍS"      "02-CABA"               
##  [4] "06-BUENOS AIRES"        "10-CATAMARCA"           "14-CÓRDOBA"            
##  [7] "18-CORRIENTES"          "22-CHACO"               "26-CHUBUT"             
## [10] "30-ENTRE RÍOS"          "34-FORMOSA"             "38-JUJUY"              
## [13] "42-LA PAMPA"            "46-LA RIOJA"            "50-MENDOZA"            
## [16] "54-MISIONES"            "58-NEUQUÉN"             "62-RÍO NEGRO"          
## [19] "66-SALTA"               "70-SAN JUAN"            "74-SAN LUIS"           
## [22] "78-SANTA CRUZ"          "82-SANTE FE"            "86-SANTIAGO DEL ESTERO"
## [25] "90-TUCUMÁN"             "94-TIERRA DEL FUEGO"

Eliminar el nombre de la primera hoja de la planilla en la variable (ya que no contiene información)

sheets = sheets[sheets!="GraphData"]

4 - Explorar de la planilla descargada y preparar las variables que nos van a ayudar a tabular los datos

Todas las hojas presentan cuadros con la información de interés. Cada cuadro ocupa 21 filas y 3 columnas más una vacía utilizada como margen, comenzando el primero en la celda “B8”

knitr::include_graphics("images/Screenshot_1.png")

El diseño de cada hoja (que representa a una jurisdicción) se compone de 5 bloques horizontales de 6 cuadros cada uno y un bloque final (el sexto) de un cuadro. Con una sequencia vamos a obtener la columna en la que empieza cada cuadro. Identificamos a la letra “B” usando el vector LETTERS que incluye con R. En este caso, la letra “B” es la segunda del vector. Iremos agregando los múltiplos de 4 hasta completar las 5 columnas.

columnas = c(LETTERS[2],LETTERS[2+4],LETTERS[2+4*2],LETTERS[2+4*3],LETTERS[2+4*4],LETTERS[2+4*5]) # columnas donde comienzan los cuadros
print(columnas)
## [1] "B" "F" "J" "N" "R" "V"

Ahora obtendremos las filas donde empiezan los cuadros. Entre la longitud de los cuadros (21 filas) más los espacios y los totales que no utilizaremos, vemos que cada cuadro comienza 28 filas debajo del bloque anterior. Multiplicamos hasta obtener las filas iniciales de cada bloque.

filas = c(8, 8+28, 8+28*2, 8+28*3, 8+28*4, 8+28*5) # filas donde empiezan los bloques
print(filas)
## [1]   8  36  64  92 120 148

Con el comando expand.grid obtendermos todas las combinaciones de fila y columna donde comienzan los cuadros

columnas_y_filas = list(
  columnas,
  filas
)

columnas_y_filas = expand.grid(columnas_y_filas)

Eliminamos las ultimas 5 combinaciones ya que el último bloque sólo dispone de un cuadro

columnas_y_filas = columnas_y_filas[1:(nrow(columnas_y_filas)-5),] 

Pegamos ambas columnas para obtener los identificadores de celdas tal como los usa Excel

celdas = paste0(columnas_y_filas$Var1,columnas_y_filas$Var2) 
print(celdas)
##  [1] "B8"   "F8"   "J8"   "N8"   "R8"   "V8"   "B36"  "F36"  "J36"  "N36" 
## [11] "R36"  "V36"  "B64"  "F64"  "J64"  "N64"  "R64"  "V64"  "B92"  "F92" 
## [21] "J92"  "N92"  "R92"  "V92"  "B120" "F120" "J120" "N120" "R120" "V120"
## [31] "B148"

Ya tenemos entonces la lista de todas las celdas que representan la primera celda (arriba y a la izquierda) de cada uno de los bloques donde se encuentran las proyecciones para un año y jurisdicción específicos


Agregamos ahora unos pasos que servirán para agregar las columnas de jurisdicción, sexo y edad en la tabla final

# creamos un vector con los grupos de edad que utiliza INDEC en las proyecciones. Podemos hacerlo desde el rango A8:A28 de la primera hoja, por ejemplo.
grupos_de_edad = readxl::read_xls("poblacion.xls", sheet = sheets[1], range = "A8:A28", col_names = F)[[1]]

# hacemos lo mismo con la variables sexo desde B4:D4
sexo = colnames(readxl::read_xls("poblacion.xls", sheet = sheets[1], range = "B4:D4"))

# hacemos lo mismo con los años
anos = 2010:2040

Ya tenemos toda la información que necesitamos:

  • Las jurisdicciones (a partir de los nombres de las hojas, en la variable sheets)
  • Los grupos de edad (en la variable grupos_de_edad)
  • Las categorías de sexo (en la variable sexo)
  • Las celdas donde empieza cada bloque (en la variable celdas)

5 - Tabulado

Vamos a hacer ahora un loop que recorra hoja por hoja y extraiga cada cuadro ubicándolo en un data.frame uno debajo del otro, e identificando la jurisdicción y el grupo de edad en filas y el sexo en columnas

resultado = data.frame() # creamos en data.frame vacío donde se guardarán los resultados

for (i in sheets[1]) { # recorre las hojas del archivo original
  for (j in celdas) { # recorre cada una de las celdas donde comienza un bloque de datos
    ano=anos[which(celdas==j)] # identifica el año del bloque que está capturando
    rango = c(j,
              paste0(LETTERS[which(LETTERS==substring(j,1,1))+2],as.numeric(substring(j,2,4))+20)) 
    rango = paste(rango,collapse = ":") # obtiene el rango de celdas del bloque
    cuadro = readxl::read_xls("poblacion.xls", sheet = i, range = rango, col_names = F) # lee el bloque
    colnames(cuadro) = sexo # pone nombre de columnas a los datos obtenidos
    cuadro$ano = ano # agrega el año de los datos
    cuadro$juri = i # agrega la jurisdicción a los datos
    cuadro$edad = grupos_de_edad # agrega las etiquetas de los grupos de edad
    resultado = rbind(
      resultado,
      cuadro[,c(4,5,6,1,2,3)]
    ) # une los datos obtenidos al data frame donde se almacenarán todos (resultado)
  }
}

Ahora sí, contamos con un data frame que contien los datos en un formato amigable:

print(resultado)
## # A tibble: 651 × 6
##      ano juri              edad  `Ambos sexos` Varones Mujeres
##    <int> <chr>             <chr>         <dbl>   <dbl>   <dbl>
##  1  2010 01-TOTAL DEL PAÍS 0-4         3571540 1838771 1732769
##  2  2010 01-TOTAL DEL PAÍS 5-9         3507135 1794531 1712604
##  3  2010 01-TOTAL DEL PAÍS 10-14       3541954 1801750 1740204
##  4  2010 01-TOTAL DEL PAÍS 15-19       3559813 1797164 1762649
##  5  2010 01-TOTAL DEL PAÍS 20-24       3346483 1674870 1671613
##  6  2010 01-TOTAL DEL PAÍS 25-29       3166874 1575889 1590985
##  7  2010 01-TOTAL DEL PAÍS 30-34       3112375 1540513 1571862
##  8  2010 01-TOTAL DEL PAÍS 35-39       2711144 1334655 1376489
##  9  2010 01-TOTAL DEL PAÍS 40-44       2347809 1149610 1198199
## 10  2010 01-TOTAL DEL PAÍS 45-49       2212137 1076990 1135147
## # ℹ 641 more rows

Podemos mejorar la presentación de los datos

# separamos los códigos de jurisdicción de los nombres
resultado$juri_nombre = substring(resultado$juri,4,max(nchar(resultado$juri)))
resultado$juri = substring(resultado$juri,1,2)

También podemos usar tidyr para pasar la variable sexo a las filas

library(tidyr)

resultado = resultado %>% pivot_longer(cols = 4:6,
                                       names_to = "sexo_nombre",
                                       values_to = "poblacion") # pasa sexo a filas

# codifica sexo
resultado$sexo_codigo = ""
resultado$sexo_codigo[resultado$sexo_nombre=="Ambos sexos"] = "0"
resultado$sexo_codigo[resultado$sexo_nombre=="Varones"] = "1"
resultado$sexo_codigo[resultado$sexo_nombre=="Mujeres"] = "2"

resultado = resultado[,c(1,2,4,7,5,3,6)] # ordena columnas
head(resultado)
## # A tibble: 6 × 7
##     ano juri  juri_nombre    sexo_codigo sexo_nombre edad  poblacion
##   <int> <chr> <chr>          <chr>       <chr>       <chr>     <dbl>
## 1  2010 01    TOTAL DEL PAÍS 0           Ambos sexos 0-4     3571540
## 2  2010 01    TOTAL DEL PAÍS 1           Varones     0-4     1838771
## 3  2010 01    TOTAL DEL PAÍS 2           Mujeres     0-4     1732769
## 4  2010 01    TOTAL DEL PAÍS 0           Ambos sexos 5-9     3507135
## 5  2010 01    TOTAL DEL PAÍS 1           Varones     5-9     1794531
## 6  2010 01    TOTAL DEL PAÍS 2           Mujeres     5-9     1712604

6 - Visualización

Finalmente, podemos hacer una visualización sencilla de los datos usando los paquetes shiny, highcharter y htmlwidgets.

library(shiny)
library(dplyr)
library(highcharter)
library(shinyWidgets)

ui <- fluidPage(
  column(3,
         br(),
         selectizeInput(inputId = "ano", 
                        label = "Seleccionar año:", 
                        choices = unique(resultado$ano),
                        selected = substring(Sys.Date(),1,4)),
         selectizeInput(inputId = "juri", 
                        label = "Seleccionar jurisdicción:", 
                        choices = unique(resultado$juri_nombre))
  ),
  column(6,
         br(),
         highchartOutput("grafico")),
  column(3)
) 

# definimos la lógica para elaborar el gráfico de pirámides a partir de la información ingresada en la ui
server <- function(input, output, session) {
  output$grafico = renderHighchart({
    datos_grafico = resultado[
      resultado$ano==input$ano &
        resultado$sexo_codigo!="0" &
        resultado$juri_nombre==input$juri,]
    
    highchart() %>%
      hc_chart(type = "bar") %>%
      hc_title(text = paste("Pirámide de población", "-", input$juri, "-", input$ano)) %>%
      hc_xAxis(categories = rev(unique(datos_grafico$edad))) %>%
      hc_yAxis(title = list(text = "Población"),
               labels = list(formatter = JS( 
                 "function() {    
                    return Math.abs(this.value); 
                  }"
               )),
               max = max(datos_grafico$poblacion)*1.1,
               min = max(datos_grafico$poblacion)*1.1*-1) %>%
      hc_plotOptions(series = list(stacking = "normal",
                                   groupPadding = 0,
                                   pointPadding = 0,
                                   borderWidth = .1)) %>%
      hc_add_series(name = "Varones", data = rev(datos_grafico$poblacion[datos_grafico$sexo_codigo=="1"])*-1, color = "#d8b365") %>%
      hc_add_series(name = "Mujeres", data = rev(datos_grafico$poblacion[datos_grafico$sexo_codigo=="2"]), color = "#5ab4ac") %>%
      hc_legend(align = "right", verticalAlign = "top", reversed = TRUE) %>%
      hc_tooltip(formatter = JS("function () {
                                  if (this.series.name === 'Varones') {
                                    return `<b>${this.series.name}</b></br>${this.y*-1}`
                                  } else if (this.series.name === 'Mujeres') {
                                    return `<b>${this.series.name}</b></br>${this.y}`}}")) %>%
      hc_exporting(enabled = TRUE)
  })  
}

# mostramos la aplicación en el servidor local
shinyApp(ui, server)

Conclusiones:

Los formatos en los cuales se puede acceder a la información sobre proyecciones de población de Argentina resultan poco amigables para los procesamientos con herramientas informáticas. Este documento muestra una metodología sencilla para dar a esos datos un formato acorde a la ciencia de datos, que aumente las posibilidades de uso de esta información en casos que requieren procesamientos complejos (series temporales, cálculo de indicadores en lote, etc.). El software estadístico R se presenta como una alternativa eficiente para esta tarea.

Referencias:

Chang, Winston, Joe Cheng, JJ Allaire, Carson Sievert, Barret Schloerke, Yihui Xie, Jeff Allen, Jonathan McPherson, Alan Dipert, and Barbara Borges. 2022. Shiny: Web Application Framework for r. https://CRAN.R-project.org/package=shiny.
R Core Team. 2019. R: A Language and Environment for Statistical Computing. Vienna, Austria: R Foundation for Statistical Computing. https://www.R-project.org.
Schauberger, Philipp, and Alexander Walker. 2023. Openxlsx: Read, Write and Edit Xlsx Files. https://CRAN.R-project.org/package=openxlsx.
Wickham, Hadley, and Jennifer Bryan. 2023. Readxl: Read Excel Files. https://CRAN.R-project.org/package=readxl.